package com.ocr;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;

import org.encog.Encog;
import org.encog.engine.network.activation.ActivationSigmoid;
import org.encog.ml.data.MLData;
import org.encog.ml.data.MLDataPair;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.data.basic.BasicMLDataSet;
import org.encog.neural.networks.BasicNetwork;
import org.encog.neural.networks.layers.BasicLayer;
import org.encog.neural.networks.training.propagation.back.Backpropagation;
import org.encog.neural.networks.training.propagation.resilient.ResilientPropagation;
import org.encog.persist.EncogDirectoryPersistence;
import org.neuroph.core.NeuralNetwork;
import org.neuroph.core.learning.SupervisedTrainingElement;
import org.neuroph.core.learning.TrainingElement;
import org.neuroph.core.learning.TrainingSet;
import org.neuroph.nnet.MultiLayerPerceptron;
import org.neuroph.util.TransferFunctionType;

import processing.core.PApplet;
import processing.core.PImage;

import com.ocr.utils.Constains;
import com.ocr.utils.Line;
import com.ocr.utils.Symbol;
import com.ocr.utils.Utils;
import com.ocr.utils.Word;

public class SymbolDetection {

	PApplet applet;

	static String DIRECTORY = "images/";
	String filename;

	PImage picture;

	ArrayList<Line> lines = new ArrayList<Line>();
	int current_line_index = 0;
	Line current_line;

	int countSym = 0;

	public SymbolDetection(String filename) {
		// TODO Auto-generated constructor stub
		this.filename = filename;
		applet = new PApplet();

		// load picture
		loadPicture();

		// symbol identify
		System.out.println(picture.width + " " + picture.height);
		identifyLines();

	}

	/**
	 * load picture
	 */
	public void loadPicture() {
		picture = applet.loadImage(DIRECTORY + filename);
	}

	/***** identify line and convert symbol image to matrix value *****/

	/**
	 * xac dinh dong van ban
	 */
	public void identifyLines() {

		int width = picture.width;
		int height = picture.height;
		boolean line_present = true;

		int top = 0, bottom = 0;
		int x = 0, y = 0;

		while (line_present) {

			// identify the top of line
			x = 0;
			while (picture.get(x, y) == applet.color(255, 255, 255)) {
				x++;
				if (x == width) {
					y++;
					x = 0;
				}
				if (y >= height) {
					line_present = false;
					break;
				}
			}
			if (line_present) {

				top = y;

				// identify bottom of line
				boolean no_black_pixel = false;
				while (!no_black_pixel) {

					y++;
					no_black_pixel = true;
					for (x = 0; x < width; x++) {
						if (picture.get(x, y) != applet.color(255, 255, 255)) {
							no_black_pixel = false;
						}
					}
				}
				bottom = y - 1;

				// new line identified
				Line line = new Line(top, bottom);
				line = identifyWords(line);
				lines.add(line);
			}

		}
	}

	/*
	 * xac dinh tu
	 */
	public Line identifyWords(Line line) {

		ArrayList<Word> words = new ArrayList<Word>();
		ArrayList<Symbol> symbols = new ArrayList<Symbol>();

		int top = line.top;
		int bottom = line.bottom;
		int left, right;

		int x, y, count;
		boolean word_present = true;

		x = 0;
		y = top;

		while (word_present) {

			// left bound
			count = 0;// dem so pixel cua khoang cach giua cac ki tu
			y = top;
			while (picture.get(x, y) == applet.color(255, 255, 255)) {
				y++;
				if (y == bottom) {
					x++;
					y = top;
					count++;
				}
				if (x >= picture.width) {
					word_present = false;
					break;
				}
			}

			// neu phat hien ra ki tu space hoac ket thuc dong
			if (count >= Constains.SPASE_WIDE || !word_present) {
				// new word
				Word w = new Word();
				w.setSymbols(symbols);
				if (symbols.size() != 0) {
					words.add(w);
				}

				// initial new symbol list for new word
				symbols = new ArrayList<Symbol>();

			}

			// right bound
			if (word_present) {
				left = x;

				boolean no_black_pixel = false;
				while (!no_black_pixel) {
					x++;
					no_black_pixel = true;
					for (y = top; y <= bottom; y++) {
						if (picture.get(x, y) != applet.color(255, 255, 255)) {
							no_black_pixel = false;
							break;
						}
					}
				}
				right = x - 1;

				// new symbol identified
				Symbol symbol = new Symbol(top, right, bottom, left);

				Symbol sym = identifyCharacterBounds(symbol);

				if (sym != null) {
					symbols.add(sym);
					countSym++;
				}
			}
		}
		line.setWords(words);
		return line;
	}

	/*
	 * xa dinh ria ki tu
	 */
	public Symbol identifyCharacterBounds(Symbol s) {

		int x = s.left;
		int y = s.top;
		boolean character_present = true;
		// top
		while (picture.get(x, y) == applet.color(255, 255, 255)) {
			x++;
			if (x == s.right) {
				y++;
				x = s.left;
			}
			if (y >= s.bottom) {
				character_present = false;
				break;
			}
		}
		if (character_present) {
			// bottom

			s.top = y;
			/*
			 * boolean no_black_pixel = false; while (!no_black_pixel) {
			 * 
			 * y++; no_black_pixel = true; for (x = s.left; x < s.right; x++) {
			 * if (picture.get(x, y) != applet.color(255, 255, 255)) {
			 * no_black_pixel = false; } } }
			 */
			y = s.bottom;
			x = s.left;
			while (picture.get(x, y) == applet.color(255, 255, 255)) {
				x++;
				if (x == s.right) {
					y--;
					x = s.left;
				}
				if (y <= s.top) {
					character_present = false;
					break;
				}
			}
			s.bottom = y;

			// left
			x = s.left;
			y = s.top;
			while (picture.get(x, y) == applet.color(255, 255, 255)) {
				y++;
				if (y == s.bottom) {
					x++;
					y = s.top;
				}
				if (x >= s.right) {
					character_present = false;
					break;
				}
			}

			// right
			if (character_present) {
				s.left = x;
				/*
				 * boolean no_black_pixel = false; while (!no_black_pixel) {
				 * x++; no_black_pixel = true; for (y = s.top; y < s.bottom;
				 * y++) { if (picture.get(x, y) != applet.color(255, 255, 255))
				 * no_black_pixel = false; } }
				 */
				x = s.right;
				y = s.top;
				while (picture.get(x, y) == applet.color(255, 255, 255)) {
					y++;
					if (y == s.bottom) {
						x--;
						y = s.top;
					}
					if (x <= s.left) {
						character_present = false;
						break;
					}
				}

				s.right = x;
			} else {// not symbol
				return null;
			}
		} else {// not symbol
			return null;
		}

		return s;
	}

	/**
	 * 
	 * @param sym
	 * @return
	 */
	public PImage convertSymbolImageToMatrixPixel(Symbol sym) {
		PImage img = picture.get(sym.left, sym.top, sym.right - sym.left + 1,
				sym.bottom - sym.top + 1);
		img.resize(10, 15);
		return img;
	}

	/*
	 * matrix values of symbol image return matrix values of symbol
	 */
	public int middleColor(PImage img) {
		int sum = 0;
		for (int j = 0; j < img.height; j++) {
			for (int i = 0; i < img.width; i++) {
				sum += img.get(i, j);
			}
		}
		return sum / (img.width * img.height);
	}

	/**
	 * 
	 * @param img
	 * @return
	 */
	public double[] to_matrix_value(PImage img) {

		double matrix[] = new double[150];

		int middle = middleColor(img);
		for (int j = 0; j < img.height; j++) {
			for (int i = 0; i < img.width; i++) {

				if (img.get(i, j) >= middle) {
					matrix[i + j * img.width] = 0;
				} else {
					matrix[i + j * img.width] = 1;
				}
			}
		}
		return matrix;
	}

	/*************************** End *******************************/

	/***************************************************************
	 * OCR with Neuroph neural network
	 ***************************************************************/

	public void doOcr() {
		NeuralNetwork neural = NeuralNetwork.load("myMlPerceptron.nnet");

		for (Line l : lines) {
			ArrayList<Word> words = l.getWords();
			for (Word word : words) {
				ArrayList<Symbol> symbols = word.getSymbols();

				for (Symbol symbol : symbols) {
					PImage img = convertSymbolImageToMatrixPixel(symbol);

					double[] input = to_matrix_value(img);

					neural.setInput(input);
					neural.calculate();
					System.out
							.println(Utils.bitArrayToChar(neural.getOutput()));
				}
			}
		}
	}

	public void doTrainOcr(String desireTextPath) {

		// neural network
		TrainingSet trainingSet = new TrainingSet();
		int i = 0;
		// read desired text
		String desireText = readDesireResult(desireTextPath);
		System.out.println("Desire data length:" + desireText.length());

		System.out.println("Set training data ...");
		for (Line l : lines) {
			ArrayList<Word> words = l.getWords();
			for (Word word : words) {
				ArrayList<Symbol> symbols = word.getSymbols();

				for (Symbol symbol : symbols) {
					PImage img = convertSymbolImageToMatrixPixel(symbol);

					double[] input = to_matrix_value(img);
					char ch = desireText.charAt(i++);
					double[] output = Utils.charToBitarray(ch);

					trainingSet.addElement(new SupervisedTrainingElement(input,
							output));
				}
			}
		}

		System.out.println("Training ...");
		// create multilayer perceptron
		MultiLayerPerceptron myMlPerceptron = new MultiLayerPerceptron(
				TransferFunctionType.SIGMOID, 150, 250, 16);
		// learn the training set
		myMlPerceptron.learnInSameThread(trainingSet);

		doNeuralNetwork(myMlPerceptron, trainingSet);
		// save trained neural network
		// System.out.println("save trained neural network");
		myMlPerceptron.save("myMlPerceptron.nnet");

		// test

	}

	public static void doNeuralNetwork(NeuralNetwork nnet, TrainingSet tset) {

		for (TrainingElement trainingElement : tset.trainingElements()) {
			System.out.println("calcu...");
			nnet.setInput(trainingElement.getInput());
			nnet.calculate();
			double[] networkOutput = nnet.getOutput();

			System.out.println(Utils.bitArrayToChar(networkOutput));

		}
	}

	public static void print(double[] array) {
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i] + " ");
		}
		System.out.println("");
	}

	/************** End OCR with neuroph neural network ***************/

	/*******************************************************************
	 * OCR with Encog neural network
	 *******************************************************************/

	/**
	 * input for encog neural network
	 */
	public double[][] getInputForEncogNetwork() {
		int i = 0;
		System.out.println("Sym number:" + countSym);
		double[][] train = new double[countSym][150];
		for (Line l : lines) {
			ArrayList<Word> words = l.getWords();
			for (Word word : words) {
				ArrayList<Symbol> symbols = word.getSymbols();

				for (Symbol symbol : symbols) {
					PImage img = convertSymbolImageToMatrixPixel(symbol);

					double[] input = to_matrix_value(img);
					train[i++] = input;
				}
			}
		}
		return train;
	}

	/**
	 * desired output for encog neural network
	 * @param desireTextPath
	 * @return
	 */
	public double[][] getOutputForEncogNetwork(String desireTextPath) {
		String desireText = readDesireResult(desireTextPath);
		double[][] outputs = new double[desireText.length()][16];
		System.out.println("Desire size:" + desireText.length());
		for (int i = 0; i < desireText.length(); i++) {
			double[] output = Utils.charToBitarray(desireText.charAt(i));
			outputs[i] = output;
		}

		return outputs;
	}

	/**
	 * trailing network and save to file
	 * @param desireTextPath : desired result
	 */
	public void doEncogTraining(String desireTextPath) {

		double[][] input = getInputForEncogNetwork();
		double[][] output = getOutputForEncogNetwork(desireTextPath);

		BasicNetwork network = new BasicNetwork();

		network.addLayer(new BasicLayer(new ActivationSigmoid(), true, 150));
		network.addLayer(new BasicLayer(new ActivationSigmoid(), true, 250));
		network.addLayer(new BasicLayer(new ActivationSigmoid(), true, 16));
		network.getStructure().finalizeStructure();
		network.reset();

		MLDataSet trainingSet = new BasicMLDataSet(input, output);

		Backpropagation train = new Backpropagation(network, trainingSet, 0.7,
				0.3);

		int epoch = 1;

		do {
			train.iteration();
			System.out
					.println("Epoch #" + epoch + " Error:" + train.getError());
			epoch++;
		} while (train.getError() > 0.01);

		// save neural network to eg file
		EncogDirectoryPersistence.saveObject(new File("images/english.eg"),
				network);
		Encog.getInstance().shutdown();

	}

	/**
	 * load encog neural network form file
	 * 
	 * @param filename
	 * @return
	 */
	public BasicNetwork loadNeuralNetwork(String filename) {

		return (BasicNetwork) EncogDirectoryPersistence.loadObject(new File(
				filename));

	}

	/**
	 * 
	 * @param neuralfile
	 */
	public void doEncogOCR(String neuralfile) {

		System.out.println("Recognition ....");
		BasicNetwork network = loadNeuralNetwork(neuralfile);

		double[][] input = getInputForEncogNetwork();

		MLDataSet data = new BasicMLDataSet(input, null);
		for (MLDataPair pair : data) {
			final MLData result = network.compute(pair.getInput());
			char ch = Utils.bitArrayToChar(result.getData());
			System.out.println(ch);
		}
		
	}

	/************** End OCR with Encog neural network *********/
	/**
	 * read desire text
	 * 
	 * @param filepath
	 * @return
	 */
	public String readDesireResult(String filepath) {
		File file = new File(filepath);
		{
			// FileInputStream inf = new FileInputStream(file);
			StringBuffer buffer = new StringBuffer();
			try {
				BufferedReader reader = new BufferedReader(new FileReader(file));
				String line;
				while ((line = reader.readLine()) != null) {
					if (line != null)
						buffer.append(line);
				}
				return buffer.toString();
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
		}
	}

	/**
	 * 
	 * @param args
	 */
	public String getRecognitionResult() {

		String result = "";
		for (Line l : lines) {
			result += l.getRecognitionLine() + "/n";
		}
		return result;

	}

	/*
	 * 
	 */
	public boolean has_next_line() {
		if (lines.size() == 0) {
			return false;
		}
		if (current_line_index < lines.size())
			return true;
		return false;
	}

	/*
	 * 
	 */
	public Line next_line() {
		if (lines.size() != 0) {
			if (has_next_line()) {
				Line line = lines.get(current_line_index);
				current_line_index++;
				return line;
			}
		}
		return null;
	}

	/*
	 * has next symbol
	 */
	public boolean has_next_symbol() {

		if (has_next_line()) {
			return true;
		} else if (current_line.has_next_symbol())
			return true;
		else
			return false;

	}

	/*
	 * next symbol return matrix values of symbol
	 */
	public double[] next_symbol() {

		/*
		 * Symbol sym = null; if (current_line.has_next_symbol()) { sym =
		 * current_line.next_symbol(); } else if (has_next_line()) {
		 * current_line = next_line(); sym = current_line.next_symbol(); }
		 * 
		 * // matrix value if (sym != null) { double[] matrix =
		 * to_matrix_value(convertSymbolImageToMatrixPixel(sym)); return matrix;
		 * } else { System.out.println("null"); }
		 */
		return null;
	}

	/********************* test function ******************************/
	/**
	 * 
	 * @param desireTextPath
	 * @throws IOException
	 */
	public void savetraindata(String desireTextPath) throws IOException {
		int i = 0;
		File file = new File("testdata.txt");

		BufferedWriter berwriter = new BufferedWriter(new FileWriter(file));
		PrintWriter writer = new PrintWriter(berwriter);

		String desireText = readDesireResult(desireTextPath);
		System.out.println(desireText.length());

		System.out.println("Set training data ...");
		for (Line l : lines) {
			ArrayList<Word> words = l.getWords();
			for (Word word : words) {
				ArrayList<Symbol> symbols = word.getSymbols();

				for (Symbol symbol : symbols) {
					PImage img = convertSymbolImageToMatrixPixel(symbol);

					double[] input = to_matrix_value(img);
					char ch = desireText.charAt(i++);
					double[] output = Utils.charToBitarray(ch);

					System.out.println("input length:" + input.length);
					System.out.println("output length:" + output.length);
					/*
					 * for (int j = 0; j < input.length; j++) {
					 * writer.print(input[j]); writer.print(","); }
					 */

					for (int j = 0; j < output.length; j++) {
						writer.print(output[j]);
						writer.print(",");
					}
					writer.println();
				}
			}
		}
		writer.close();
	}

	/**
	 * 
	 */
	public void saveSymbolImage() {
		int i = 0;
		for (Line l : lines) {
			ArrayList<Word> words = l.getWords();
			for (Word word : words) {
				ArrayList<Symbol> symbols = word.getSymbols();

				for (Symbol symbol : symbols) {
					PImage img = convertSymbolImageToMatrixPixel(symbol);
					saveImageToFile(img, "symbol" + i + ".png");
					i++;
				}
			}
		}
	}

	/**
	 * 
	 * @param img
	 * @param filename
	 */
	public void saveImageToFile(PImage img, String filename) {
		Utils.saveImage(img, filename);
		System.out.println("Saved " + filename);
		System.out.println("");
	}

	public void testlines() {
		for (Line l : lines) {
			System.out.println(l.toString());
		}
	}

	/* ********************* End test************************ */

	public static void main(String[] args) {
		SymbolDetection test = new SymbolDetection("english.png");

		// test.doTrainOcr("images/english.txt");
		// test.doOcr();
		test.doEncogTraining("images/english.txt");
		test.doEncogOCR("images/english.eg");
		/*
		 * try { test.savetraindata("images/english.txt"); } catch (IOException
		 * e) { // TODO Auto-generated catch block e.printStackTrace(); }
		 */

		// test.train_ocr("images/english.txt");
		// while (test.has_next_symbol))
		// test.next_symbol();
		// Character ch = new Character('â');
		// byte data[] = ch.toString().getBytes();
		// System.out.print(data.length);

		/*
		 * double bits[] = Utils.charToBitarray('a'); for (int i = 0; i <
		 * bits.length; i++) { System.out.print((int) bits[i]); }
		 * System.out.println(Utils.bitArrayToChar(bits));
		 */
	}
}
